﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace SignalBox.Core.Model.Graph
{
    public class LayoutGraph
    {
        private TrackLayout trackLayout;

        private List<GraphNode> nodes;
        private List<TrackCell> visitedCells;

        public LayoutGraph(TrackLayout layout, bool editMode)
        {
            trackLayout = layout;
            if (editMode)
            {
                buildGraph();
            }
            else
            {
                buildSimGraph();
            }
        }

        public void WriteDebug()
        {
            foreach (GraphNode gn in nodes)
            {
                WriteDebug(gn, DIRECTION.E);
                WriteDebug(gn, DIRECTION.S);
                WriteDebug(gn, DIRECTION.W);
                WriteDebug(gn, DIRECTION.N);
                WriteDebug(gn, DIRECTION.NE);
                WriteDebug(gn, DIRECTION.SE);
                WriteDebug(gn, DIRECTION.NW);
                WriteDebug(gn, DIRECTION.SW);
                

            }

        }

        private void WriteDebug(GraphNode node, DIRECTION dir)
        {
            GraphNode next = node.GetNextNode(dir);
            if (next != null)
            {
                System.Console.WriteLine("Node (" + node.Cell.X + "-" + node.Cell.Y + ") is connected to node (" + next.Cell.X + "-" + next.Cell.Y + ") in direction " + dir.ToString());
            }
        }

        public bool isValid()
        {
            var valid = true;
            foreach (GraphNode node in nodes)
            {
                valid &= node.isValid();
            }
            return valid;
        }

        public GraphNode findNode(TrackCell cell)
        {
            foreach (GraphNode node in nodes)
            {
                if (node.Cell == cell)
                {
                    return node;
                }
            }
            return null;
        }

        public GraphNode findNodeAt(int x, int y)
        {
            foreach (GraphNode gn in nodes)
            {
                var p = gn.getCellCoordinates();
                if (p.X == x && p.Y == y)
                {
                    return gn;
                }
            }
            return null;
        }


        #region Simulation Mode Graph


        public void CleanConnections()
        {
            foreach (GraphNode gn in nodes)
            {
                gn.cleanConnections();
            }
        }

        private void buildSimGraph()
        {
            nodes = new List<GraphNode>();
            visitedCells = new List<TrackCell>();

            for (var x = 0; x < trackLayout.Cols; x++)
            {
                for (var y = 0; y < trackLayout.Rows; y++)
                {
                    var tc = trackLayout.getCell(x, y);

                    if (tc != null)
                    {
                        var node = findNodeAt(x, y);
                        if (node == null)
                        {
                            followSimPath(tc);
                        }
                    }
                }
            }

            CleanConnections();

        }

        private void followSimPath(TrackCell tc)
        {
            var node = findNodeAt(tc.X, tc.Y);
            if (node == null)
            {
                node = new GraphNode(tc);
                nodes.Add(node);
            }

            var x = tc.X;
            var y = tc.Y;

            checkSimPath(tc, node, DIRECTION.N, DIRECTION.S, x, y - 1);
            checkSimPath(tc, node, DIRECTION.E, DIRECTION.W, x + 1, y);
            checkSimPath(tc, node, DIRECTION.S, DIRECTION.N, x, y + 1);
            checkSimPath(tc, node, DIRECTION.W, DIRECTION.E, x - 1, y);
            checkSimPath(tc, node, DIRECTION.NE, DIRECTION.SW, x + 1, y - 1);
            checkSimPath(tc, node, DIRECTION.NW, DIRECTION.SE, x - 1, y - 1);
            checkSimPath(tc, node, DIRECTION.SE, DIRECTION.NW, x + 1, y + 1);
            checkSimPath(tc, node, DIRECTION.SW, DIRECTION.NE, x - 1, y + 1);
        }

        private void checkSimPath(TrackCell cell, GraphNode node, DIRECTION dOut, DIRECTION dIn, int x, int y)
        {
            TrackCell next;

            visitedCells.Add(cell);

            if (cell.HasDirection(dOut))
            {

               
                next = trackLayout.getCell(x, y);

                if (next == null)
                {
                    next = cell.LinkedCell;
                }



                if (next != null && next.HasDirection(dIn))
                {
                    if (!visitedCells.Contains(next))
                    {
                        followSimPath(next);
                    }
                    var nextNode = findNodeAt(next.X, next.Y);



                    if (nextNode != null)
                    {
                        nextNode.addConnection(dIn, node);
                        node.addConnection(dOut, nextNode);
                    }
                }
            }
        }

        #endregion


        #region Edit Mode Graph
        private void buildGraph()
        {
            nodes = new List<GraphNode>();
            visitedCells = new List<TrackCell>();


            for (var x = 0; x < trackLayout.Cols; x++)
            {
                for (var y = 0; y < trackLayout.Rows; y++)
                {
                    var tc = trackLayout.getCell(x, y);

                    if (tc != null)
                    {
                        var node = findNodeAt(x, y);
                        if (node == null)
                        {
                            followPath(tc);
                        }
                    }
                }
            }
        }

        private void followPath(TrackCell tc)
        {
            var node = findNodeAt(tc.X, tc.Y);
            if (node == null)
            {
                node = new GraphNode(tc);
                nodes.Add(node);
            }

            var x = tc.X;
            var y = tc.Y;

            checkPath(tc, node, DIRECTION.N, DIRECTION.S, x, y - 1);
            checkPath(tc, node, DIRECTION.E, DIRECTION.W, x + 1, y);
            checkPath(tc, node, DIRECTION.S, DIRECTION.N, x, y + 1);
            checkPath(tc, node, DIRECTION.W, DIRECTION.E, x - 1, y);
            checkPath(tc, node, DIRECTION.NE, DIRECTION.SW, x + 1, y - 1);
            checkPath(tc, node, DIRECTION.NW, DIRECTION.SE, x - 1, y - 1);
            checkPath(tc, node, DIRECTION.SE, DIRECTION.NW, x + 1, y + 1);
            checkPath(tc, node, DIRECTION.SW, DIRECTION.NE, x - 1, y + 1);
        }

        private void checkPath(TrackCell cell, GraphNode node, DIRECTION dOut, DIRECTION dIn, int x, int y)
        {
            TrackCell next;

            visitedCells.Add(cell);

            if (cell.HasDirection(dOut))
            {
                next = trackLayout.getCell(x, y);

                if (next == null)
                {
                    next = cell.LinkedCell;
                }



                if (next != null && next.HasDirection(dIn))
                {
                    if (!visitedCells.Contains(next))
                    {
                        followPath(next);
                    }
                    var nextNode = findNodeAt(next.X, next.Y);



                    if (nextNode != null)
                    {
                        nextNode.addConnection(dIn, node);
                        node.addConnection(dOut, nextNode);
                    }
                }
            }
        }
        #endregion
    }
}
